package top.xlq.mydb.backend.dm.pageCache;

import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import top.xlq.mydb.backend.common.AbstractCache;
import top.xlq.mydb.backend.dm.page.Page;
import top.xlq.mydb.backend.dm.page.PageImpl;
import top.xlq.mydb.backend.utils.Panic;
import top.xlq.mydb.common.Error;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class PageCacheImpl extends AbstractCache<Page> implements PageCache {
    private static final int MEM_MIN_LIM = 10;//缓存最小设置大小
    public static final String DB_SUFFIX = ".db";//文件后缀


    private RandomAccessFile randomAccessFile;
    private FileChannel fc;
    private Lock fileLock;

    private AtomicInteger pageNumbers;// 记录当前打开的数据库文件有多少页,这个数字在数据库文件被打开时就会被计算，并在新建页面时自增。

    public PageCacheImpl(RandomAccessFile randomAccessFile, FileChannel fc, int maxResource) {
        super(maxResource);
        if (maxResource < 10) {
            Panic.panic(Error.MemTooSmallException);
        }
        long length = 0;
        try {
            length = randomAccessFile.length();
        } catch (IOException e) {
            e.printStackTrace();
        }
        this.randomAccessFile = randomAccessFile;
        this.fc = fc;
        fileLock = new ReentrantLock();
        pageNumbers = new AtomicInteger((int) (length / PAGE_SIZE));
    }

    /**
     * 从文件读取对应页码的数据页缓存
     *
     * @param key
     * @return
     * @throws Exception
     */
    @Override
    protected Page getForCache(long key) throws Exception {
        // 将key转换为页码
        int pageNo = (int) key;
        // 计算页码对应的偏移量
        int offset = PageCacheImpl.pageOffset(pageNo);
        ByteBuffer buffer = ByteBuffer.allocate(PAGE_SIZE);
        fileLock.lock();
        try {
            fc.position(offset);
            fc.read(buffer);
        } catch (IOException e) {
            Panic.panic(e);
        } finally {
            fileLock.unlock();
        }
        return new PageImpl(pageNo, buffer.array(), this);
    }

    /**
     * 获取当前数据页的偏移量
     *
     * @param pageNo
     * @return
     */
    private static int pageOffset(int pageNo) {
        // 页号从 1 开始
        return (pageNo - 1) * PAGE_SIZE;
    }

    /**
     * 脏数据就刷回到磁盘
     *
     * @param page
     */
    @Override
    protected void releaseForCache(Page page) {
        if (page.isDirty()) {
            flush(page);
            page.setDirty(false);
        }
    }

    /**
     * 将这个页面的内容写回到磁盘
     *
     * @param page
     */
    private void flush(Page page) {
        int pageNo = page.getPageNumber();
        int offset = PageCacheImpl.pageOffset(pageNo);


        fileLock.lock();
        try {
            ByteBuffer buffer = ByteBuffer.wrap(page.getData());
            fc.position(offset);
            fc.write(buffer);
            fc.force(false);
        } catch (IOException e) {
            Panic.panic(e);
        } finally {
            fileLock.unlock();
        }
    }

    /**
     * 新建页面
     *
     * @param initData
     * @return
     */
    @Override
    public int newPage(byte[] initData) {
        int pageNo = pageNumbers.incrementAndGet();// 先自增在返回
        Page page = new PageImpl(pageNo, initData, null);
        flush(page);// 新建的页面需要立刻写回
        return pageNo;
    }

    /**
     * 获取页面缓存
     * @param pgno
     * @return
     * @throws Exception
     */
    @Override
    public Page getPage(int pgno) throws Exception {
        return get((long) pgno);
    }

    /**
     * 释放页面缓存
     * @param page
     */
    @Override
    public void release(Page page) {
        release((long) page.getPageNumber());
    }

    /**
     * 裁剪页面缓存页数为0-maxPgno
     * @param maxPgno 最大页数
     */
    @Override
    public void truncateByBgno(int maxPgno) {
        long size = pageOffset(maxPgno + 1);// 找到下一页的position
        try {
            randomAccessFile.setLength(size);//把底层文件裁剪（或扩展）到 size 字节。
        } catch (IOException e) {
            Panic.panic(e);
        }
        pageNumbers.set(maxPgno);
    }

    /**
     * 获取页面缓存的页面数
     * @return
     */
    @Override
    public int getPageNumber() {
        return pageNumbers.intValue();
    }

    /**
     * 将当前页面刷进文件
     * @param pg
     */
    @Override
    public void flushPage(Page pg) {
        flush(pg);
    }

    @Override
    public void close() {
        super.close();
        try {
            fc.close();
            randomAccessFile.close();
        } catch (IOException e) {
            Panic.panic(e);
        }
    }
}
